
#include <winsock2.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <windows.h>
#include <time.h>

#define DEFAULT_PORT	5019
#define LISTRANGE		1028
#define SIZE 128
#define ONE_ROW_MAX_CHR 256
#define FILE_PATH "User_Pwd.txt"

// *************** Server GUI ***************
int manageer_pwd = 123;
void manager_login_UI(void) {
	// MacOS
	// system("Clear");
	// Windows
	system("cls");
	printf("\n");
	printf("\n");
	printf("\t==============================================\t\n");
	printf("\t==============================================\t\n\n");
	printf("\t\t1. Login Manager!\n");
	printf("\t\t2. Exit!\n\n");
	printf("\t==============================================\t\n");
	printf("\t==============================================\t\n");
	printf("\n");
	printf("\n");
}
void manager_input_pwd_UI(void) {
	// MacOS
	// system("Clear");
	// Windows
	system("cls");
	printf("\n");
	printf("\n");
	printf("\t==============================================\t\n");
	printf("\t==============================================\t\n\n");
	printf("\t\t1. Login Admin Account!\n");
	printf("\t\t2. Forget Your Password!!\n");
	printf("\t\t3. Exit!\n\n");
	printf("\t==============================================\t\n");
	printf("\t==============================================\t\n");
	printf("\n");
	printf("\n");
}
void manager_EXE_UI(void) {
	// MacOS
	// system("Clear");
	// Windows
	system("cls");
	printf("\n");
	printf("\n");
	printf("\t==============================================\t\n");
	printf("\t==============================================\t\n\n");
	printf("\t\t1. Add Client Into Database!\n");
	printf("\t\t2. Delete a Client From Database!\n");
	printf("\t\t3. Delete All Client From Database!\n");
	printf("\t\t4. Exit!\n\n");
	printf("\t==============================================\t\n");
	printf("\t==============================================\t\n");
	printf("\n");
	printf("\n");
}
/*
	Add a client into database
*/
void add_client_database(void) {
	FILE* file = fopen(FILE_PATH, "a+");
	char usr_name[20];
	char usr_pwd[20];
	char line[20];
	char row[20];
	int name_exist_state;

	printf("Please input user name: ");
	scanf("%s", usr_name);

	// check name in database
	while (fgets(line, sizeof(line), file)) {
		// printf("Line: %s\n", line);
		sscanf(line, "%s%*s", row);
		// printf("Row: %s\n", row);

		if (strcmp(usr_name, row) == 0)
		{
			/* exist */
			name_exist_state = 1;
			// printf("%d\n", name_exist_state);
			break;
		}
		else {
			// not exist
			name_exist_state = 0;
			// printf("%d\n", name_exist_state);

		}
	}
	printf("Name Exist State: %d\n", name_exist_state);

	if (name_exist_state == 1)
	{
		printf("This User is in the database!\n");
	}
	else {
		printf("Client %s needs to be added!\n", usr_name);
		printf("Please input Client %s 's password: ", usr_name);
		scanf("%s", &usr_pwd);
		printf("The Client: %s, Password: %s\n", usr_name, usr_pwd);
		// combine usr_name and usr_pwd with " "
		char* usr_name_with_space;
		usr_name_with_space = strcat(usr_name, " ");
		char* usr_name_pwd;
		usr_name_pwd = strcat(usr_name_with_space, usr_pwd);
		// add usr name 
		fprintf(file, "%s\n", usr_name_pwd);
		printf("Name and Password Added Successfully!\n");
	}

	fclose(file);
}
/*
	Delete All Clients from the database
*/
void delete_all_client(void) {
	FILE* file = fopen(FILE_PATH, "w");

	printf("Delete All Clients Successfully!\n");

	fclose(file);
}
void print_all_clients(void) {
	FILE* file = fopen(FILE_PATH, "a+");

	// Go through
	char th_line[20];
	char th_row[20];

	// print all clients' names
	printf("======================\n\n");
	printf("All Clients' Names: \n");
	int row_num = 1;
	while (fgets(th_line, sizeof(th_line), file)) {
		sscanf(th_line, "%s%*s", th_row);
		printf("Row %d: %s\n", row_num, th_row);
		row_num++;
	}
	printf("======================\n\n");
	fclose(file);
}
int find_client_row_number(void) {
	FILE* file = fopen(FILE_PATH, "a+");
	char usr_name[20];
	char usr_pwd[20];
	char line[20];
	char row[20];

	print_all_clients();

	printf("Please input user name you want to delete: ");
	scanf("%s", usr_name);

	int name_exist_state;
	int name_row_num = 0;
	// check name in database
	while (fgets(line, sizeof(line), file)) {
		// printf("Line: %s\n", line);
		sscanf(line, "%s%*s", row);
		name_row_num++;
		// printf("Row: %s\n", row);

		if (strcmp(usr_name, row) == 0)
		{
			/* exist */
			name_exist_state = 1;
			// printf("%d\n", name_exist_state);
			printf("Name Row Number: %d\n", name_row_num);
			break;
		}
		else {
			// not exist
			name_exist_state = 0;
			// printf("%d\n", name_exist_state);

		}
	}
	printf("Name Exist State: %d\n", name_exist_state);
	fclose(file);

	return name_row_num;
}
static int del_assigned_row(void)
{
	int lines = 1;
	char path_tmp[256] = { 0 };
	char line[ONE_ROW_MAX_CHR] = { 0 };
	FILE* fp_in = NULL, * fp_out = NULL;

	int assign_line;
	assign_line = find_client_row_number();

	sprintf(path_tmp, "%s.tmp", FILE_PATH);
	fp_in = fopen(FILE_PATH, "r");
	if (fp_in == NULL) {
		printf("open %s failed!\n", FILE_PATH);
		return -1;
	}
	fp_out = fopen(path_tmp, "a+");
	if (fp_out == NULL) {
		printf("create tmp file %s failed!\n", path_tmp);
		return -1;
	}

	while (fgets(line, ONE_ROW_MAX_CHR, fp_in) != NULL)
	{
		if (lines != assign_line) {
			fwrite(line, 1, strlen(line), fp_out);
		}
		++lines;
		memset(line, 0, ONE_ROW_MAX_CHR);
	}

	if (fclose(fp_in) != 0) {
		printf("close file %s failed!\n", FILE_PATH);
		return -2;
	}
	if (fclose(fp_out) != 0) {
		printf("close file %s failed!\n", path_tmp);
		return -2;
	}
	remove(FILE_PATH);
	rename(path_tmp, FILE_PATH);
	printf("Delete %d Row Successfully!\n", assign_line);

	return 0;
}
int check_pwd(int usr_input_pwd) {
	// check Admin password
	if (usr_input_pwd == manageer_pwd) {
		// pwd correct
		return 0;
	}
	else {
		// pwd error
		return 1;
	}

}
void manager_EXE_UI_exe(void) {
	int usr_EXE_choice;

	while (1) {
		// Add / Delete / Delete All
		int exit_EXE;
		manager_EXE_UI();
		printf("\tPlease input your choice: ");
		scanf("%d", &usr_EXE_choice);

		// EXE choice
		switch (usr_EXE_choice) {
		case 1:
			// 1. Add Client Into Database!
			add_client_database();
			Sleep(1500);
			break;
		case 2:
			// 2. Delete a Client From Database!
			del_assigned_row();
			Sleep(1500);
			break;
		case 3:
			// 3. Delete All Client From Database!
			delete_all_client();
			Sleep(1500);
			break;
		case 4:
			// 4. Exist!
			printf("Exist!\n");
			Sleep(1000);
			break;
		default:
			break;
		}

		printf("\nDo you want to continue managing database? (Y:1, N:0)");
		scanf("%d", &exit_EXE);
		if (exit_EXE == 0)
		{
			/* code */
			break;
		}
	}

}
void manager_input_pwd_UI_exe(void) {
	int user_account_choice;
	int usr_input_pwd;
	int check_pwd_result;

	// choose login directly or change password
	manager_input_pwd_UI();
	printf("\tPlease input your choice: ");
	scanf("%d", &user_account_choice);

	// Account State
	switch (user_account_choice) {
	CASE1:case 1:
		printf("Login: Please input Admin password: ");
		scanf("%d", &usr_input_pwd);

		while (1) {
			// check input password
			check_pwd_result = check_pwd(usr_input_pwd);

			if (check_pwd_result == 1)
			{
				/* input pwd error */
				printf("Please input Admin password again: ");
				scanf("%d", &usr_input_pwd);
			}
			else {
				// input pwd correct
				break;
			}
		}

		// manager_EXE_UI_exe
		manager_EXE_UI_exe();

		break;
	case 2:
		printf("Please reset Admin password: ");
		scanf("%d", &manageer_pwd);
		printf("New Admin password is: %d\n", manageer_pwd);
		goto CASE1;
	default:
		break;
	}
}
void manager_login_UI_exe(void) {
	int user_login_choice;

	// manager login
	manager_login_UI();
	printf("\tPlease input your choice: ");
	scanf("%d", &user_login_choice);

	// Login State
	switch (user_login_choice) {
	case 1:
		// Clear Screen
		manager_input_pwd_UI_exe();
		break;

	case 2:
		printf("Exit!!\n");
		break;

	default:
		break;
	}
}
void manager(void) {
	while (1)
	{
		char is_manager[10] = { '\0' };
		gets(is_manager);
		if (strcmp(is_manager, "admin") == 0) {
			manager_login_UI_exe();
		}

	}
}

typedef struct msg_package {
	SOCKET msg_sock;
	struct sockaddr_in client_addr;
};

struct sock_name {
	SOCKET sock_num;
	char usr_name[100];
};


// Global varibales
struct sock_name online_list[LISTRANGE];
int sockName_index = 0;
char file_path[] = "User_Pwd.txt";

char* secure(char pwd[100]) {
	// encrypt pwd
	char encrypt_pwd[128] = { '\0' };
	int i;
	int count = strlen(pwd);

	for (i = 0; i < count; i++)
	{
		/* encryption */
		encrypt_pwd[i] = pwd[i] + i + 5;
	}

	encrypt_pwd[i] = '\0';
	char* return_pwd = (char*)malloc(sizeof(char*) * 100);
	strcpy(return_pwd, encrypt_pwd);
	return return_pwd;
}

bool checkpwd(char* pwd, char* input_pwd) {
	// char c_pwd[100];
	// strcpy(c_pwd, secure(pwd));
	// char c_input_pwd[100];
	// strcpy(c_input_pwd, secure(input_pwd));
	// result
	bool result = true;
	// check pwd
	if (strcmp(pwd, input_pwd) == 0)
	{
		result = true;
	}
	else {
		result = false;
	}
	return result;
}

char* checkLogin(SOCKET msg_sock) {

	bool check_confirm = false;
	while (1) {
		/* Recv name */
		char name_Buff[100];
		// Each Line
		char line[256];
		// Each Row
		char row[20];
		// Each Password
		char per_pwd[100];
		// name_pwd
		char msg_Buff[100];
		// login state
		// exist	 : 1
		// not exist : 0
		int login_state = 0;

		// Receive from client
		int name_len;
		int msg_len;

		name_len = recv(msg_sock, name_Buff, sizeof(name_Buff), 0);
		printf("User name: %s\n", name_Buff);
		msg_len = recv(msg_sock, msg_Buff, sizeof(msg_Buff), 0);
		printf("User password: %s\n", msg_Buff);


		// Check name whether in .txt or not
		// Read name from .txt
		char exist_pwd[100];
		printf("Read file...\n");
		FILE* file = fopen(file_path, "a+");
		while (fgets(line, sizeof(line), file)) {
			sscanf(line, "%s%*s", row);
			sscanf(line, "%*s%s", per_pwd);
			// judge name in them or not
			if (strcmp(name_Buff, row) == 0) {
				login_state = 1;
				strcpy(exist_pwd, per_pwd);
				// name_pwd = per_pwd;
				break;
			}
			else {
				login_state = 0;
				strcpy(exist_pwd, "");
			}
		}

		// If not exist the user
		if (login_state == 0) {
			char new_password[100];
			strcpy(new_password, secure(msg_Buff));
			fprintf(file, "%s %s\n", name_Buff, new_password);
			send(msg_sock, "True", sizeof("True"), 0);
			printf("Create new user success!\n");
			return name_Buff;
		}

		printf("Login_state: %d\n", login_state);
		printf("name_pwd: %s\n", msg_Buff);


		bool check_result = false;

		if (login_state == 1)
		{
			/* check pwd */
			char c_msg_Buff[100];
			strcpy(c_msg_Buff, secure(msg_Buff));
			check_result = checkpwd(exist_pwd, c_msg_Buff);
		}
		else {
			// fprintf(file, "%s\n", pwd);
		}

		char feedback[10];
		if (check_result) {
			strcpy(feedback, "True");
			check_confirm = true;
		}
		else {
			strcpy(feedback, "False");
		}
		send(msg_sock, feedback, sizeof(feedback), 0);

		// Break state
		if (check_confirm == true) {
			printf("Log success.\n");
			return name_Buff;
		}
		else {
			printf("Check again.\n");
		}

	}

}

int server_func(void* msg) {

	// Catch the msg from main process
	struct msg_package msge = *(struct msg_package*)msg;
	SOCKET msg_sock = (&msge)->msg_sock;
	struct sockaddr_in client_addr = (&msge)->client_addr;

	// Cheak log
	char usr_name[100];
	strcpy(usr_name, checkLogin(msg_sock));
	printf("Hello, %s\n", usr_name);
	printf("Check finish\n");

	// Create socketNumber - name list and add
	struct sock_name new_usr;
	(&new_usr)->sock_num = msg_sock;
	strcpy((&new_usr)->usr_name, usr_name);
	online_list[sockName_index] = new_usr;
	sockName_index++;

	// ************** Chatting Mode ************** //
	// 0 -> Single ; 1 -> Group
	int chat_mode;
	// Read mode
	int mode_len;
	char user_input_mode[10];
	mode_len = recv(msg_sock, user_input_mode, sizeof(user_input_mode), 0);
	if (mode_len == SOCKET_ERROR) {
		fprintf(stderr, "recv mode failed with error %d\n", WSAGetLastError());
		WSACleanup();
		return -1;
	}
	if (mode_len == 0) {
		printf("Client closed connection\n");
		closesocket(msg_sock);
		return -1;
	}
	// Mapping to the mode
	if (strcmp(user_input_mode, "0") == 0) {
		chat_mode = 0;
	}
	else {
		chat_mode = 1;
	}

	// Go chatting
	if (chat_mode == 0) {
		// ****************** Single chatting ******************
		char return_usr_list[100] = { '\0' };
		// Show all of the online user
		for (int i = 0; i < sockName_index; i++) {
			char i_number[5];
			_itoa(i, i_number, 10);
			strcat(return_usr_list, i_number);
			strcat(return_usr_list, "\t:\t");
			strcat(return_usr_list, (&online_list[i])->usr_name);
			strcat(return_usr_list, "\n");
		}
		// Back to client
		int usr_list_len;
		usr_list_len = send(msg_sock, return_usr_list, sizeof(return_usr_list), 0);
		if (usr_list_len == SOCKET_ERROR) {
			fprintf(stderr, "list send failed with error %d\n", WSAGetLastError());
			WSACleanup();
			return -1;
		}
		// Receive user index
		int usr_index_len;
		char usr_index_char[5];
		usr_index_len = recv(msg_sock, usr_index_char, sizeof(usr_index_char), 0);
		if (usr_index_len == SOCKET_ERROR) {
			fprintf(stderr, "select recv failed with error %d\n", WSAGetLastError());
			WSACleanup();
			return -1;
		}
		int usr_index = atoi(usr_index_char); // The user index which user select

		while (1) {
			// Receive message from client
			int msg_len;
			char szBuff[100];
			msg_len = recv(msg_sock, szBuff, sizeof(szBuff), 0);
			if (msg_len == SOCKET_ERROR) {
				fprintf(stderr, "recv() failed with error %d\n", WSAGetLastError());
				WSACleanup();
				return -1;
			}
			if (strcmp(szBuff, "Quit()") == 0) {
				msg_len = 0;
			}
			if (msg_len == 0) {
				printf("%s closed connection\n", usr_name);
				closesocket(msg_sock);
				_endthread();
				return -1;
			}
			// printf("Bytes Received: %d, message: %s from %s\n", msg_len, szBuff, inet_ntoa(client_addr.sin_addr));

			// Send to a single user
			char temp[100] = { '\0' };
			strcat(temp, usr_name); // Cat the userName and message
			strcat(temp, ": ");
			strcat(temp, szBuff);
			msg_len = send((&online_list[usr_index])->sock_num, temp, sizeof(temp), 0);
			if (msg_len == SOCKET_ERROR) {
				fprintf(stderr, "send() failed with error %d\n", WSAGetLastError());
				WSACleanup();
				return -1;
			}
			if (msg_len == 0) {
				printf("Client closed connection\n");
				closesocket((&online_list[usr_index])->sock_num);
				return -1;
			}
		}
	}
	else {
		// Group chatting
		while (1) {
			// Receive message from client
			int msg_len;
			char szBuff[100];
			msg_len = recv(msg_sock, szBuff, sizeof(szBuff), 0);
			if (msg_len == SOCKET_ERROR) {
				fprintf(stderr, "recv() failed with error %d\n", WSAGetLastError());
				WSACleanup();
				return -1;
			}
			if (strcmp(szBuff, "Quit()") == 0) {
				msg_len = 0;
			}
			if (msg_len == 0) {
				printf("%s closed connection\n", usr_name);
				closesocket(msg_sock);
				_endthread();
				return -1;
			}
			// printf("Bytes Received: %d, message: %s from %s\n", msg_len, szBuff, inet_ntoa(client_addr.sin_addr));

			// Boardcast part
			for (int i = 0; i < sockName_index; i++) {
				char temp[100] = { '\0' };
				strcat(temp, usr_name);
				strcat(temp, ": ");
				strcat(temp, szBuff);
				msg_len = send((&online_list[i])->sock_num, temp, sizeof(temp), 0);
				if (msg_len == 0) {
					printf("Client closed connection\n");
					closesocket((&online_list[i])->sock_num);
					return -1;
				}
			}
		}
	}
}

int main(int argc, char** argv) {

	char szBuff[100];
	int msg_len;
	int addr_len;
	struct sockaddr_in local, client_addr;

	SOCKET sock, msg_sock;
	WSADATA wsaData;

	// WSAStartup part
	if (WSAStartup(0x202, &wsaData) == SOCKET_ERROR) {
		// stderr: standard error are printed to the screen.
		fprintf(stderr, "WSAStartup failed with error %d\n", WSAGetLastError());
		// WSACleanup function terminates use of the Windows Sockets DLL. 
		WSACleanup();
		return -1;
	}
	// Fill in the address structure
	local.sin_family = AF_INET;
	local.sin_addr.s_addr = INADDR_ANY;
	local.sin_port = htons(DEFAULT_PORT);

	sock = socket(AF_INET, SOCK_STREAM, 0);	// TCP socket

	// Socket part
	if (sock == INVALID_SOCKET) {
		fprintf(stderr, "socket() failed with error %d\n", WSAGetLastError());
		WSACleanup();
		return -1;
	}
	// Bind part
	if (bind(sock, (struct sockaddr*)&local, sizeof(local)) == SOCKET_ERROR) {
		fprintf(stderr, "bind() failed with error %d\n", WSAGetLastError());
		WSACleanup();
		return -1;
	}

	// Listening part - waiting for the connections
	if (listen(sock, 5) == SOCKET_ERROR) {
		fprintf(stderr, "listen() failed with error %d\n", WSAGetLastError());
		WSACleanup();
		return -1;
	}

	// Server management
	unsigned thread_management;
	if (_beginthreadex(NULL, 0, &manager, NULL, 0, &thread_management) == 0) {
		fprintf(stderr, "management system create failed with error %d\n", WSAGetLastError());
		return -1;
	}

	while (1) {
		printf("waiting for new connection...\n");

		HANDLE hThread;
		unsigned thread_id;
		addr_len = sizeof(client_addr);

		// Block here, until accept a new connection
		msg_sock = accept(sock, (struct sockaddr*)&client_addr, &addr_len);
		if (msg_sock == INVALID_SOCKET) {  // 'accept' error
			fprintf(stderr, "accept() failed with error %d\n", WSAGetLastError());
			WSACleanup();
			return -1;
		}

		printf("A new connection occurs!\n");
		printf("accepted connection from %s, port %d\n",
			inet_ntoa(client_addr.sin_addr),
			htons(client_addr.sin_port));

		// Define struct
		struct msg_package msg;
		msg.msg_sock = msg_sock;
		msg.client_addr = client_addr;

		// Create a new thread to serve client
		if (_beginthreadex(NULL, 0, &server_func, (void*)&msg, 0, &thread_id) == 0) {
			fprintf(stderr, "thread create failed with error %d\n", WSAGetLastError());
			WSACleanup();
			return -1;
		}

	}
	closesocket(msg_sock);
	WSACleanup();
}